package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.query.CodeQuery;
import com.tianji.promotion.domain.vo.ExchangeCodeVO;
import com.tianji.promotion.enums.ExchangeCodeStatus;
import com.tianji.promotion.mapper.ExchangeCodeMapper;
import com.tianji.promotion.service.IExchangeCodeService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.utils.CodeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static com.tianji.promotion.constants.PromotionConstants.*;

/**
 * <p>
 * 兑换码 服务实现类
 * </p>
 *
 * @author huanmo
 * @since 2025-09-26
 */
@Service
@Slf4j
public class ExchangeCodeServiceImpl extends ServiceImpl<ExchangeCodeMapper, ExchangeCode> implements IExchangeCodeService {

    private final StringRedisTemplate redisTemplate;
    private final BoundValueOperations<String, String> serialOps;

    public ExchangeCodeServiceImpl(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
        // 所有优惠券共有一个key，因此绑定key
        this.serialOps = redisTemplate.boundValueOps(COUPON_CODE_SERIAL_KEY);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Async("generateExchangeCodeExecutor")
    public void asyncGenerateCode(Coupon coupon) {
        // 发放数量
        Integer totalNum = coupon.getTotalNum();
        if (totalNum == null || totalNum <= 0) {
            log.warn("优惠券{}的发放数量无效：{}", coupon.getId(), totalNum);
            return;
        }
        // 1.获取Redis自增序列号
        Long result = serialOps.increment(totalNum);
        if (result == null) {
            return;
        }
        int maxSerialNum = result.intValue();
        List<ExchangeCode> list = new ArrayList<>(totalNum);
        for (int serialNum = maxSerialNum - totalNum + 1; serialNum <= maxSerialNum; serialNum++) {
            // 2.生成兑换码
            String code = CodeUtil.generateCode(serialNum, coupon.getId());
            ExchangeCode e = new ExchangeCode();
            e.setCode(code);
            e.setId(serialNum);
            e.setExchangeTargetId(coupon.getId());
            e.setExpiredTime(coupon.getIssueEndTime());
            e.setStatus(ExchangeCodeStatus.UNUSED);
            e.setCreateTime(LocalDateTime.now());
            e.setUpdateTime(LocalDateTime.now());
            list.add(e);
        }
        // 3.保存数据库
        saveBatch(list);
        // 4.写入Redis缓存，member：couponId，score：兑换码的最大序列号
        redisTemplate.opsForZSet().add(COUPON_RANGE_KEY, coupon.getId().toString(), maxSerialNum);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Async("pauseExchangeCodeExecutor")
    public void asyncPauseCode(Coupon coupon) {
        Long couponId = coupon.getId();
        LocalDateTime now = LocalDateTime.now();
        log.info("开始暂停优惠券{}的兑换码", couponId);
        try {
            // 1.修改数据库：只暂停未使用的兑换码
            lambdaUpdate()
                    .eq(ExchangeCode::getExchangeTargetId, couponId)
                    .eq(ExchangeCode::getStatus, ExchangeCodeStatus.UNUSED)
                    .set(ExchangeCode::getExpiredTime, now)
                    .set(ExchangeCode::getStatus, ExchangeCodeStatus.EXPIRED)
                    .set(ExchangeCode::getUpdateTime, now)
                    .update();
            log.info("已暂停优惠券{}的兑换码", couponId);
            // 2.删除Redis缓存中的兑换码范围信息
            redisTemplate.opsForZSet().remove(COUPON_RANGE_KEY, couponId.toString());
        } catch (Exception e) {
            log.error("暂停优惠券{}的兑换码失败", couponId, e);
            throw new RuntimeException("暂停兑换码失败", e);
        }
    }

    @Override
    public PageDTO<ExchangeCodeVO> queryCodeByPage(CodeQuery query) {
        Long id = query.getCouponId();
        Integer status = query.getStatus();
        // 将Integer状态转换为枚举
        ExchangeCodeStatus statusEnum = null;
        if (status != null) {
            statusEnum = ExchangeCodeStatus.of(status);
            if (statusEnum == null) {
                throw new BadRequestException("无效的兑换码状态: " + status);
            }
        }
        // 1.分页查询
        Page<ExchangeCode> page = lambdaQuery()
                .ne(ExchangeCode::getStatus, ExchangeCodeStatus.EXPIRED)
                .eq(id != null, ExchangeCode::getExchangeTargetId, id)
                .eq(statusEnum != null, ExchangeCode::getStatus, statusEnum)
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        // 2.处理VO
        List<ExchangeCode> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        List<ExchangeCodeVO> list = BeanUtils.copyList(records, ExchangeCodeVO.class);
        // 3.返回
        return PageDTO.of(page, list);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Async("generateExchangeCodeExecutor")
    public void asyncBatchGenerateCodes(List<Coupon> coupons) {
        log.info("开始批量生成兑换码，优惠券数量：{}", coupons.size());
        try {
            // 批量处理所有优惠券
            for (Coupon coupon : coupons) {
                asyncGenerateCode(coupon);
            }
            log.info("批量生成兑换码完成，优惠券数量：{}", coupons.size());
        } catch (Exception e) {
            log.error("批量生成兑换码失败", e);
            throw new RuntimeException("批量生成兑换码失败", e);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Async("pauseExchangeCodeExecutor")
    public void asyncBatchPauseCodes(List<Coupon> coupons) {
        log.info("开始批量暂停兑换码，优惠券数量：{}", coupons.size());
        try {
            // 批量处理所有优惠券
            for (Coupon coupon : coupons) {
                asyncPauseCode(coupon);
            }
            log.info("批量暂停兑换码完成，优惠券数量：{}", coupons.size());
        } catch (Exception e) {
            log.error("批量暂停兑换码失败", e);
            throw new RuntimeException("批量暂停兑换码失败", e);
        }
    }

    /**
     * Redis的setBit操作在多线程情况下是线程安全的
     * 1. Redis本身是单线程的
     * Redis采用单Reactor模型，所有命令都是原子执行的
     * 2. 返回值的作用
     * setBit返回的是该位置之前的值：
     * 返回true：之前已经是1（表示已兑换）
     * 返回false：之前是0（表示未兑换）
     * <p>
     * 键 (Key):COUPON_CODE_MAP_KEY - 一个固定的Redis Key，存储所有兑换码的使用状态位图
     * 值 (Value):serialNum - 兑换码的序列号，作为BitMap中的偏移量 (offset)   mark - 布尔值，表示该兑换码的使用状态
     */
    @Override
    public boolean updateExchangeMark(long serialNum, boolean mark) {
        Boolean boo = redisTemplate.opsForValue().setBit(COUPON_CODE_MAP_KEY, serialNum, mark);
        return boo != null && boo;
    }

    @Override
    public Long exchangeTargetId(long serialNum) {
        // 1.查询score值比当前序列号大的第一个优惠券
        Set<String> results = redisTemplate.opsForZSet().rangeByScore(
                COUPON_RANGE_KEY, serialNum, serialNum + 5000, 0L, 1L);
        if (CollUtils.isEmpty(results)) {
            return null;
        }
        // 2.数据转换
        String next = results.iterator().next();
        return Long.parseLong(next);
    }
}
